// 版权所有2012 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package x509

// RFC 1423描述了PEM块的加密。
// 从密码生成密钥的算法是通过查看OpenSSL 
// 实现推导出来的。

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/des"
	"crypto/md5"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"io"
	"strings"
)

type PEMCipher int

// EncryptPEMBlock加密算法的可能值。
const (
	_ PEMCipher = iota
	PEMCipherDES
	PEMCipher3DES
	PEMCipherAES128
	PEMCipherAES192
	PEMCipherAES256
)

// rfc1423Algo拥有一种加密PEM块的方法。
type rfc1423Algo struct {
	cipher     PEMCipher
	name       string
	cipherFunc func(key []byte) (cipher.Block, error)
	keySize    int
	blockSize  int
}

// rfc1423Algos拥有一部分加密PEM 
// 块的可能方法。ivSize编号取自OpenSSL源代码。
var rfc1423Algos = []rfc1423Algo{{
	cipher:     PEMCipherDES,
	name:       "DES-CBC",
	cipherFunc: des.NewCipher,
	keySize:    8,
	blockSize:  des.BlockSize,
}, {
	cipher:     PEMCipher3DES,
	name:       "DES-EDE3-CBC",
	cipherFunc: des.NewTripleDESCipher,
	keySize:    24,
	blockSize:  des.BlockSize,
}, {
	cipher:     PEMCipherAES128,
	name:       "AES-128-CBC",
	cipherFunc: aes.NewCipher,
	keySize:    16,
	blockSize:  aes.BlockSize,
}, {
	cipher:     PEMCipherAES192,
	name:       "AES-192-CBC",
	cipherFunc: aes.NewCipher,
	keySize:    24,
	blockSize:  aes.BlockSize,
}, {
	cipher:     PEMCipherAES256,
	name:       "AES-256-CBC",
	cipherFunc: aes.NewCipher,
	keySize:    32,
	blockSize:  aes.BlockSize,
},
}

// deriveKey使用密钥派生函数将密码扩展为密钥
// ，其位数为密码所需的位数。该算法源于OpenSSL源代码
// 。
func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
	hash := md5.New()
	out := make([]byte, c.keySize)
	var digest []byte

	for i := 0; i < len(out); i += len(digest) {
		hash.Reset()
		hash.Write(digest)
		hash.Write(password)
		hash.Write(salt)
		digest = hash.Sum(digest[:0])
		copy(out[i:], digest)
	}
	return out
}

// IsEncryptedPEMBlock返回PEM块是否经过密码加密
// 根据RFC 1423。
// 
// 已弃用：RFC 1423中指定的传统PEM加密在
// 设计中不安全。由于它不验证密文，因此容易受到
// oracle攻击的攻击，攻击者可以通过这些攻击恢复明文。
func IsEncryptedPEMBlock(b *pem.Block) bool {
	_, ok := b.Headers["DEK-Info"]
	return ok
}

// 当检测到不正确的密码时，将返回不正确的密码错误。
var IncorrectPasswordError = errors.New("x509: decryption password incorrect")

// DecryptPEMBlock获取根据RFC 1423加密的PEM块和用于加密的
// 密码，并返回一个解密的DER编码
// 字节。它检查DEK Info报头以确定用于
// 解密的算法。如果没有DEK Info头，则返回一个错误。如果检测到
// 密码不正确，则返回不正确的密码错误。由于
// 格式存在许多缺陷，因此不可能总是检测到错误的密码。在这些情况下，不会返回错误，但
// 解密的DER字节将是随机噪声。
// 
// 已弃用：RFC 1423中指定的传统PEM加密在
// 设计中不安全。由于它不验证密文，因此容易受到
// oracle攻击的攻击，攻击者可以通过这些攻击恢复明文。
func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
	dek, ok := b.Headers["DEK-Info"]
	if !ok {
		return nil, errors.New("x509: no DEK-Info header in block")
	}

	mode, hexIV, ok := strings.Cut(dek, ",")
	if !ok {
		return nil, errors.New("x509: malformed DEK-Info header")
	}

	ciph := cipherByName(mode)
	if ciph == nil {
		return nil, errors.New("x509: unknown encryption mode")
	}
	iv, err := hex.DecodeString(hexIV)
	if err != nil {
		return nil, err
	}
	if len(iv) != ciph.blockSize {
		return nil, errors.New("x509: incorrect IV size")
	}

	// 基于OpenSSL实现。salt是初始化向量的前8个字节
	// 。
	key := ciph.deriveKey(password, iv[:8])
	block, err := ciph.cipherFunc(key)
	if err != nil {
		return nil, err
	}

	if len(b.Bytes)%block.BlockSize() != 0 {
		return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
	}

	data := make([]byte, len(b.Bytes))
	dec := cipher.NewCBCDecrypter(block, iv)
	dec.CryptBlocks(data, b.Bytes)

	// 使用一种方案填充块，其中最后n个字节的填充都是
	// 等于n。它可以从1填充到块大小字节（包括）。见RFC 1423。
	// 例如：
	// /[x y z 2]
	// 如果我们检测到错误的填充，我们就认为它是无效密码。
	dlen := len(data)
	if dlen == 0 || dlen%ciph.blockSize != 0 {
		return nil, errors.New("x509: invalid padding")
	}
	last := int(data[dlen-1])
	if dlen < last {
		return nil, IncorrectPasswordError
	}
	if last == 0 || last > ciph.blockSize {
		return nil, IncorrectPasswordError
	}
	for _, val := range data[dlen-last:] {
		if int(val) != last {
			return nil, IncorrectPasswordError
		}
	}
	return data[:dlen-last], nil
}

// EncryptPEMBlock返回指定类型的PEM块，其中包含给定的DER编码数据，该数据根据RFC 1423使用指定的算法和
// 密码加密。
// 
// 已弃用：RFC 1423中指定的传统PEM加密在
// 设计中不安全。由于它不验证密文，因此容易受到
// oracle攻击的攻击，攻击者可以通过这些攻击恢复明文。
func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
	ciph := cipherByKey(alg)
	if ciph == nil {
		return nil, errors.New("x509: unknown encryption mode")
	}
	iv := make([]byte, ciph.blockSize)
	if _, err := io.ReadFull(rand, iv); err != nil {
		return nil, errors.New("x509: cannot generate IV: " + err.Error())
	}
	// salt是初始化向量的前8个字节，
	// 匹配解密块中的密钥派生。
	key := ciph.deriveKey(password, iv[:8])
	block, err := ciph.cipherFunc(key)
	if err != nil {
		return nil, err
	}
	enc := cipher.NewCBCEncrypter(block, iv)
	pad := ciph.blockSize - len(data)%ciph.blockSize
	encrypted := make([]byte, len(data), len(data)+pad)
	// 我们可以通过分别加密
	// 中的所有数据块来保存此副本，但它似乎不值得额外的
	// 代码。
	copy(encrypted, data)
	// 参见RFC 1423第1.1节。
	for i := 0; i < pad; i++ {
		encrypted = append(encrypted, byte(pad))
	}
	enc.CryptBlocks(encrypted, encrypted)

	return &pem.Block{
		Type: blockType,
		Headers: map[string]string{
			"Proc-Type": "4,ENCRYPTED",
			"DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
		},
		Bytes: encrypted,
	}, nil
}

func cipherByName(name string) *rfc1423Algo {
	for i := range rfc1423Algos {
		alg := &rfc1423Algos[i]
		if alg.name == name {
			return alg
		}
	}
	return nil
}

func cipherByKey(key PEMCipher) *rfc1423Algo {
	for i := range rfc1423Algos {
		alg := &rfc1423Algos[i]
		if alg.cipher == key {
			return alg
		}
	}
	return nil
}
